using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Collections;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
using System.ServiceModel.Description;

namespace ManagedCardWriter
{
    class ManagedInformationCard
    {
        string _cardId = string.Empty;
        string _cardName = string.Empty;
        string _issuerName = string.Empty;
        string _issuerId = string.Empty;
        DateTime _issuedOn = DateTime.Now;
        DateTime _expiresOn = DateTime.MaxValue;
        bool _requireAppliesTo = true;
		string _language = "en-us";
        string _cardVersion = string.Empty;
        string _mimeType = string.Empty;
        byte[] _logo;
		List<string> _tokenTypes = new List<string>();
        List<ClaimInfo> _supportedClaims = new List<ClaimInfo>();
        string _privacyNoticeAt;
        string _hint = string.Empty;
        string _credentialIdentifier = string.Empty;
		Constants.CardType _cardType = Constants.CardType.None;
        string _mexUri = string.Empty;

		public ManagedInformationCard(Constants.CardType cardType)
        {
            _cardType = cardType;
        }

		#region Properties
		public Constants.CardType CardType
		{
			get { return _cardType; }
		}

		public string CardId
		{
			get { return _cardId; }
			set { _cardId = value; }
		}

		public string CardName
		{
			get { return _cardName; }
			set { _cardName = value; }
		}

		public string IssuerName
		{
			get { return _issuerName; }
			set { _issuerName = value; }
		}

		public string IssuerId
		{
			get { return _issuerId; }
			set { _issuerId = value; }
		}

		public byte[] CardLogo
		{
			get { return _logo; }
			set { _logo = value; }
		}

		public string MimeType
		{
			get { return _mimeType; }
			set { _mimeType = value; }
		}

		public bool RequireAppliesTo
		{
			get { return _requireAppliesTo; }
			set { _requireAppliesTo = value; }
		}

		public List<string> TokenTypes
		{
			get { return _tokenTypes; }
			set { _tokenTypes = value; }
		}

		public string MexUri
		{
			get { return _mexUri; }
			set { _mexUri = value; }
		}

		public List<ClaimInfo> SupportedClaims
		{
			get { return _supportedClaims; }
			set { _supportedClaims = value; }
		}

		public string PrivacyNoticeAt
		{
			get { return _privacyNoticeAt; }
			set { _privacyNoticeAt = value; }
		}

		public string CardVersion
		{
			get { return _cardVersion; }
			set { _cardVersion = value; }
		}

		public string CredentialHint
		{
			get { return _hint; }
			set { _hint = value; }
		}

		public string CredentialIdentifier
		{
			get { return _credentialIdentifier; }
			set { _credentialIdentifier = value; }
		} 
		#endregion

        public XmlElement SerializeAndSign( X509Certificate2 cert )
        {
            MemoryStream stream = new MemoryStream();
            XmlWriter writer = XmlWriter.Create( stream );

			writer.WriteStartElement(Constants.WSIdentity.Elements.InformationCard, Constants.WSIdentity.Namespace);

			writer.WriteAttributeString(Constants.Xml.Language, Constants.Xml.Namespace, _language);
			writer.WriteStartElement(Constants.WSIdentity.Elements.InformationCardReference, Constants.WSIdentity.Namespace);
			writer.WriteElementString(Constants.WSIdentity.Elements.CardId, Constants.WSIdentity.Namespace, _cardId);
			writer.WriteElementString(Constants.WSIdentity.Elements.CardVersion, Constants.WSIdentity.Namespace, _cardVersion);
            writer.WriteEndElement();

            if( !String.IsNullOrEmpty( _cardName ) )
            {
				writer.WriteStartElement(Constants.WSIdentity.Elements.CardName, Constants.WSIdentity.Namespace);
                writer.WriteString( _cardName );
                writer.WriteEndElement();
            }

            if( _logo != null && _logo.Length > 0 )
            {
				writer.WriteStartElement(Constants.WSIdentity.Elements.CardImage, Constants.WSIdentity.Namespace);
                if( !String.IsNullOrEmpty( _mimeType ) )
					writer.WriteAttributeString(Constants.WSIdentity.Elements.MimeType, _mimeType);
                writer.WriteString( Convert.ToBase64String( _logo ) );
                writer.WriteEndElement();
            }

			writer.WriteStartElement(Constants.WSIdentity.Elements.Issuer, Constants.WSIdentity.Namespace);
            writer.WriteString( _issuerId );
            writer.WriteEndElement();

			writer.WriteStartElement(Constants.WSIdentity.Elements.TimeIssued, Constants.WSIdentity.Namespace);
            writer.WriteString( XmlConvert.ToString( _issuedOn, XmlDateTimeSerializationMode.Utc ) );
            writer.WriteEndElement();

			writer.WriteStartElement(Constants.WSIdentity.Elements.TimeExpires, Constants.WSIdentity.Namespace);
            writer.WriteString( XmlConvert.ToString( _expiresOn, XmlDateTimeSerializationMode.Utc ) );
            writer.WriteEndElement();

			writer.WriteStartElement(Constants.WSIdentity.Elements.TokenServiceList, Constants.WSIdentity.Namespace);
			writer.WriteStartElement(Constants.WSIdentity.Elements.TokenService, Constants.WSIdentity.Namespace);

			BuildEndPointReference(cert).WriteTo(AddressingVersion.WSAddressing10, writer);

			writer.WriteStartElement(Constants.WSIdentity.Elements.UserCredential, Constants.WSIdentity.Namespace);

            if( !String.IsNullOrEmpty( _hint ) )
            {
				writer.WriteStartElement(Constants.WSIdentity.Elements.DisplayCredentialHint, Constants.WSIdentity.Namespace);
                writer.WriteString( _hint );
                writer.WriteEndElement();
            }

            switch( _cardType )
            {
				case Constants.CardType.UserNamePassword:
					writer.WriteStartElement(Constants.WSIdentity.Elements.UserNamePasswordCredential, Constants.WSIdentity.Namespace);
					if( !string.IsNullOrEmpty( _credentialIdentifier ) )
					{
						writer.WriteStartElement(Constants.WSIdentity.Elements.UserName, Constants.WSIdentity.Namespace);
						writer.WriteString( _credentialIdentifier );
						writer.WriteEndElement();
					}
					writer.WriteEndElement();
                    break;
				case Constants.CardType.Kerberos:
					writer.WriteStartElement(Constants.WSIdentity.Elements.KerberosV5Credential, Constants.WSIdentity.Namespace);
					writer.WriteEndElement();
                    break;
				case Constants.CardType.SelfIssued:
					writer.WriteStartElement(Constants.WSIdentity.Elements.SelfIssuedCredential, Constants.WSIdentity.Namespace);
					if( !string.IsNullOrEmpty( _credentialIdentifier ) )
					{
						writer.WriteStartElement(Constants.WSIdentity.Elements.PrivatePersonalIdentifier, Constants.WSIdentity.Namespace);
						writer.WriteString( _credentialIdentifier );
						writer.WriteEndElement();
					}
					else
					{
						throw new InvalidDataException( "No PPID was specified" );
					}
					writer.WriteEndElement();
                    break;
				case Constants.CardType.Certificate:
					writer.WriteStartElement(Constants.WSIdentity.Elements.X509V3Credential, Constants.WSIdentity.Namespace);
					writer.WriteStartElement(Constants.XmlDSig.Elements.X509Data, Constants.XmlDSig.Namespace);
                    if( !string.IsNullOrEmpty( _credentialIdentifier ) )
                    {
						writer.WriteStartElement(Constants.WSSecurityExt.Elements.KeyIdentifier, Constants.WSSecurityExt.Namespace);
						writer.WriteAttributeString(Constants.WSSecurityExt.Elements.ValueType, null, Constants.WSSecurityExt.Sha1ThumbrpintKeyTypeValue);
                        writer.WriteString( _credentialIdentifier );
                        writer.WriteEndElement();
                    }
                    else
                    {
                        throw new InvalidDataException( "No thumbprint was specified" );
                    }
                    writer.WriteEndElement();
                    writer.WriteEndElement();
                    break;
                default:
                    break;
            }

			writer.WriteEndElement(); //</UserCredential>
			writer.WriteEndElement(); //</TokenService>
            writer.WriteEndElement(); //</TokenServiceList>

			writer.WriteStartElement(Constants.WSIdentity.Elements.SupportedTokenTypeList, Constants.WSIdentity.Namespace);
            foreach( string type in _tokenTypes )
				writer.WriteElementString(Constants.WSTrust.Elements.TokenType, Constants.WSTrust.Namespace, type);
			writer.WriteEndElement(); 

			writer.WriteStartElement(Constants.WSIdentity.Elements.SupportedClaimTypeList, Constants.WSIdentity.Namespace);
            foreach( ClaimInfo claim in _supportedClaims )
            {
				writer.WriteStartElement(Constants.WSIdentity.Elements.SupportedClaimType, Constants.WSIdentity.Namespace);
				writer.WriteAttributeString(Constants.WSIdentity.UriAttribute, claim.Id);
				if (!String.IsNullOrEmpty(claim.DisplayTag))
					writer.WriteElementString(Constants.WSIdentity.Elements.DisplayTag, Constants.WSIdentity.Namespace, claim.DisplayTag );
				if (!String.IsNullOrEmpty(claim.Description))
					writer.WriteElementString(Constants.WSIdentity.Elements.Description, Constants.WSIdentity.Namespace, claim.Description);
				writer.WriteEndElement();
            }
            writer.WriteEndElement();

            if (_requireAppliesTo)
				writer.WriteElementString(Constants.WSIdentity.Elements.RequireAppliesTo, Constants.WSIdentity.Namespace, null);

            if (!String.IsNullOrEmpty(_privacyNoticeAt))
            {
				writer.WriteStartElement(Constants.WSIdentity.Elements.PrivacyNoticeAt, Constants.WSIdentity.Namespace);
                writer.WriteString( _privacyNoticeAt );
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
            writer.Close();

            // Sign the xml content
            XmlDocument doc = new XmlDocument();
            doc.PreserveWhitespace = false;
			stream.Position = 0;
			doc.Load(stream);

            SignedXml signed = new SignedXml();
            signed.SigningKey = cert.PrivateKey;
            signed.Signature.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;

            Reference reference = new Reference();
            reference.Uri = "#_Object_InfoCard";
            reference.AddTransform(new XmlDsigExcC14NTransform());
            signed.AddReference(reference);

            KeyInfo info = new KeyInfo();
			info.AddClause(new KeyInfoX509Data(cert, X509IncludeOption.WholeChain));

            signed.KeyInfo = info;
            DataObject cardData = new DataObject( "_Object_InfoCard", null, null, doc.DocumentElement );
            signed.AddObject( cardData );

            signed.ComputeSignature();

			return signed.GetXml();
        }

		private EndpointAddress BuildEndPointReference(X509Certificate2 cert)
		{
			EndpointAddressBuilder eprBuilder = new EndpointAddressBuilder();

            eprBuilder.Uri = new Uri( _issuerId );
            eprBuilder.Identity = new X509CertificateEndpointIdentity( cert );

            if( null != _mexUri )
            {
                MetadataReference mexRef = new MetadataReference();
                mexRef.Address = new EndpointAddress( _mexUri );
                mexRef.AddressVersion = AddressingVersion.WSAddressing10;

                MetadataSection mexSection = new MetadataSection();
                mexSection.Metadata = mexRef;

                MetadataSet mexSet = new MetadataSet();
                mexSet.MetadataSections.Add( mexSection );
				
                MemoryStream memStream = new MemoryStream();
				XmlTextWriter wr1 = new XmlTextWriter( memStream, System.Text.Encoding.UTF8 );

                mexSet.WriteTo( wr1 );
				wr1.Flush();
                memStream.Seek( 0, SeekOrigin.Begin );

				eprBuilder.SetMetadataReader(XmlDictionaryReader.CreateTextReader(memStream, XmlDictionaryReaderQuotas.Max));
            }

			return eprBuilder.ToEndpointAddress();
		}
	}

	public sealed class ClaimInfo
	{
		string _id;
		string _displayTag;
		string _description;

		public ClaimInfo(string id, string displayTag, string description)
		{
			_id = id;
			_displayTag = displayTag;
			_description = description;
		}
		public string Id
		{
			get { return _id; }
			set { _id = value; }
		}
		public string DisplayTag
		{
			get { return _displayTag; }
			set { _displayTag = value; }
		}
		public string Description
		{
			get { return _description; }
			set { _description = value; }
		}
	}
}
